﻿using System;
using System.IO;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.Serialization.Formatters.Binary;
using Algobox.JuicyLib.Compression.SevenZip;

namespace Algobox.JuicyLib.Compression
{
    public class SevenZipHelper
    {
        // Default properties
        const int dictionary = 1 << 23;
        const Int32 posStateBits = 0;
        const UInt32 litContextBits = 0;
        const UInt32 litPosBits = 0;
        const Int32 algorithm = 2;
        const Int32 numFastBytes = 128;
        const string matchFinder = "bt2";
        const bool eos = false;

        /// <summary>
        /// Property locations in properties array
        /// </summary>
        CoderPropID[] propIDs = 
				{
					CoderPropID.DictionarySize,
					CoderPropID.PosStateBits,
					CoderPropID.LitContextBits,
					CoderPropID.LitPosBits,
					CoderPropID.Algorithm,
					CoderPropID.NumFastBytes,
					CoderPropID.MatchFinder,
					CoderPropID.EndMarker
				};

        BinaryFormatter bf = new BinaryFormatter();
        MemoryStream inStream = new MemoryStream();

        SevenZip.Compress.LZMA.Encoder encoder = new SevenZip.Compress.LZMA.Encoder();
        SevenZip.Compress.LZMA.Decoder decoder = new SevenZip.Compress.LZMA.Decoder();
 
        public SevenZipHelper()
        {
            object[] properties = new object[]
		    {
			    (Int32) dictionary,
			    (Int32)(posStateBits),
			    (Int32)(litContextBits),
			    (Int32)(litPosBits),
			    (Int32)(algorithm),
			    (Int32)(numFastBytes),
			    matchFinder,
			    eos
		    };

            // always use the same encoder properties for now
            encoder.SetCoderProperties(propIDs, properties);
        }


        long inputBytesCount = 0;

        public string Compress(string inputString)
        {
            inStream.Seek(0, 0);
            bf.Serialize(inStream, inputString);

            inputBytesCount = inStream.Position;

            MemoryStream outStream = new MemoryStream();
            encoder.WriteCoderProperties(outStream);

            for (int i = 0; i < 8; i++)
                outStream.WriteByte((Byte)(inputBytesCount >> (8 * i)));

            inStream.Seek(0, 0);
            // do the compression            
            encoder.Code(inStream, outStream, inputBytesCount, -1, null);

            return Convert.ToBase64String(outStream.ToArray());
        }

        


        public string Decompress(string inputString)
        {
            byte[] inputBytes = Convert.FromBase64String(inputString);

            inStream.Seek(0, 0);
            inStream.Write(inputBytes, 0, inputBytes.Length);

            inStream.Seek(0, 0);
            // get the properties
            byte[] properties = new byte[5];
            if (inStream.Read(properties, 0, 5) != 5)
                throw (new Exception("input .lzma is too short"));
            // set the properties to the decoder
            decoder.SetDecoderProperties(properties);
            
            long outSize = 0;
            for (int i = 0; i < 8; i++)
            {
                int v = inStream.ReadByte();
                if (v < 0)
                    throw (new Exception("Can't Read 1"));
                outSize |= ((long)(byte)v) << (8 * i);
            }

            inputBytesCount = inStream.Length - inStream.Position;

            MemoryStream outStream = new MemoryStream();

            decoder.Code(inStream, outStream, inputBytesCount, outSize, null);
                     
            //NEEDED?
            outStream.Seek(0, 0);           

            return (string)bf.Deserialize(outStream);
        }






        /*

        public byte[] Compress(byte[] inputBytes)
        {

            inputBytesCount = inputBytes.Count();

            // ensure more than enough memory is allocated
            if(ChunkSize < inputBytesCount)
                ChunkSize = 2 * inputBytesCount;

            inStream.Write(inputBytes, 0, inputBytesCount);
            inStream.Seek(0, 0); // not sure if I need this

            MemoryStream outStream = new MemoryStream();
            encoder.WriteCoderProperties(outStream);

            encoder.Code(inStream, outStream, inputBytesCount, -1, null);
            
            return outStream.ToArray();
        }




        

        public byte[] Decompress(byte[] inputBytes)
        {
            inputBytesCount = inputBytes.Count();
            inStream.Write(inputBytes, 0, inputBytesCount);

            // get the properties
            byte[] properties = new byte[5];
            if (inStream.Read(properties, 0, 5) != 5)
                throw (new Exception("input .lzma is too short"));
            // set the properties to the decoder
            decoder.SetDecoderProperties(properties);

            MemoryStream outStream = new MemoryStream();

            decoder.Code(inStream, outStream, inStream.Length - inStream.Position, -1, null);
            
            return outStream.ToArray();

        }
        */



    }
}
